#include <gtest/gtest.h>



#include <system_error>
#include <filesystem>
#include <cassert>
#include <fstream>
#include <string_view>

#include "helper/resource/ReopenableResource.h"


namespace tiny {


  class Current {
  public:
    explicit Current(const std::string & content, const std::string & dir = ".") 
    : _dir(dir)
    , _content(content) 
    {
    }
    ~Current(){
    }

    const std::string filePath(){
      return _dir + "/" + "CURRENT";
    }
    void write(){
      std::ofstream ofile(filePath(), std::ios::out | std::ios::trunc | std::ios::binary);
      ofile << _content;
    }

    /*
    std::string read(){
      std::ifstream ifile(filePath(), std::ios::in);
      std::string str;
      ifile >> str;
      // write memory cordump
      return str;
    }*/

    void destroy(){
      std::filesystem::remove(filePath());
    }

  private:
    const std::string & _dir;
    const std::string _content;
  };

  class Logger {
  public:
    Logger(long long id, const std::string & dir = ".")
    : _pDir(dir)
    , _id(id)
    , _fileName(dir + "/" + Logger::FileName(id))
    , _file(_fileName, std::ios::out|std::ios::app)
    {
    }

    ~Logger(){
    }

    static std::string FileName(long long id){
      return  "logger-" + std::to_string(id);
    }

    const std::string & fileName() const {
      return _fileName;
    }

    int append(const char * buf, size_t len){
      if(buf != nullptr && _file){
         _file << std::string_view(buf, len);
        return len;
      }
      return -1;
    }
    int append(const std::string & str) {
      return append(str.c_str(), str.size());
    }
  private:
    std::filesystem::path _pDir;
    long long _id;
    const std::string _fileName;
    std::ofstream _file;
  };


  class DB : public helper::resource::ReopenableResource<std::error_code>{
  public:
    explicit DB(const std::string & dir) 
    : _dir(dir)
    , _next_file_id(0) 
    , _logger(nullptr)
    {
    }

    void __release(){
      if(_logger){
        delete _logger;
        _logger = nullptr;
      }
    }

    ~DB() override {
      __release();
    }

    bool exists() const override {
      return std::filesystem::exists(_dir);
    }
    std::error_code destroy() override {
      if(auto && ec = close(); ec){
        return ec;
      }
      std::filesystem::remove_all(_dir);
      return std::error_code();
    }
  protected: 
    std::error_code _open() override{
      return exists() ? recovery() : create();
    }

    std::error_code _close() override{
      __release();
      return std::error_code();
    }

    std::error_code create() {
      std::filesystem::create_directories(_dir);
      //1. next file id
      long long logFileId = next_file_id();
      std::cout << "log file id" << logFileId << std::endl;

      //2. create logger file
      {
        Logger logger(logFileId, _dir);
      }

      //3. record the logger file name into CURRENT
      {
        Current current(Logger::FileName(logFileId), _dir.string()); //RAII
        current.write();
      }

      //4. reopen the logger
      assert(_logger == nullptr);
      _logger = new Logger(logFileId, _dir);

      return std::error_code();
    }
    std::error_code recovery() {
      //TODO:
      //1. read current
      //2. read all files for init next_file_id
      //
      //3. current[file].foreach(<record>) => memory (remove duplicated key)
      //4. write memory to one { next_file_id = next_file_id();}
      //5. rename next_file_id ->  0
      //
      //6. set_next_file_id(0);
      //5. reopen logger(0);
      return std::error_code();
    }


  public: // kv pair operations 
    std::error_code insert(const std::string & key, const std::string & value){
      //TODO:
      return std::error_code();
    }
    std::error_code remove(const std::string & key) {
      //TODO:
      return std::error_code();
    }
    std::error_code update(const std::string & key, const std::string & value) {
      //TODO:
      return std::error_code();
    }


    long long next_file_id() {
      return ++ _next_file_id;
    }
  private:
    const std::filesystem::path _dir;
    long long _next_file_id;
    Logger * _logger;
};


}//namespace tiny;


/*  
TEST(TinyDBTest, main){

  tiny::DB db("tinydb");
  
  ASSERT_EQ(std::error_code(), db.open());

  ASSERT_EQ(std::error_code(), db.insert("hello", "world"));
  ASSERT_EQ(std::error_code(), db.remove("hello"));
  ASSERT_EQ(std::error_code(), db.update("hello", "world cup"));

  ASSERT_EQ(std::error_code(), db.close());

  db.destroy();
}*/


/*  
// Create  : open for create 
TEST(TinyDBTest, create){
  tiny::DB db("test.tinydb");
  
  ASSERT_TRUE(!db.exists());

  ASSERT_EQ(std::error_code(), db.open());

  ASSERT_TRUE(db.exists());

  db.close();
  db.destroy();
  ASSERT_TRUE(!db.exists());
}


TEST(LoggerTest, main){
  const char * dir = "test_log_dir";
  std::filesystem::create_directories(dir);
  {
    tiny::Logger log(1, dir);
    ASSERT_EQ(std::string(dir) + "/logger_1", log.fileName());
    ASSERT_EQ(5, log.append("hello", 5));
  }
  std::filesystem::remove_all(dir);
}*/


TEST(CurrentTest, main){
  /*
  std::unique_ptr<WritableFile> file = ("file.name.current");

  {
    tiny::current::Writer writer(new Writer(file));
    writer.content();
  }
  {
    tiny::current::Reader reader(new Reader(file));
    tiny::Current && current = reader.content(); 
    ASSERT_TRUE(current.check());
    current.fileId();
    current.dbName();
    current.name();
  }*/
}
